package com.luych.toolbox.automation.service.service;

import com.luych.toolbox.automation.service.constant.Constant;
import com.luych.toolbox.automation.service.dao.*;
import com.luych.toolbox.automation.service.entity.*;
import com.luych.toolbox.automation.service.job.AutoTaskHistoryExecuteJob;
import com.luych.toolbox.common.enums.AutoTaskHistoryStatus;
import com.luych.toolbox.common.event.base.service.publisher.WebSocketEventPublisher;
import com.luych.toolbox.common.feign.intf.base.service.UserFeign;
import com.luych.toolbox.common.feign.param.automation.service.AutoTaskHistoryArgParam;
import com.luych.toolbox.common.feign.param.automation.service.AutoTaskHistoryParam;
import com.luych.toolbox.common.feign.param.gateway.center.WebSocketEvent;
import com.luych.toolbox.common.feign.view.automation.service.*;
import com.luych.toolbox.common.feign.view.base.service.UserView;
import com.luych.toolbox.common.page.PageParam;
import com.luych.toolbox.common.page.PageResult;
import com.luych.toolbox.common.service.base.BaseConvert;
import com.luych.toolbox.common.service.base.BaseService;
import com.luych.toolbox.common.service.exception.HttpException;
import com.luych.toolbox.common.service.page.SpringPageParamAdapter;
import com.luych.toolbox.common.service.page.SpringPageResultAdapter;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class AutoTaskHistoryService extends BaseService<AutoTaskHistoryView, AutoTaskHistoryParam, AutoTaskHistory> {

    private static final BaseConvert<AutoTaskHistoryParam, AutoTaskHistory> taskHistoryParamToEntity = AutoTaskHistory::new;
    private static final BaseConvert<AutoTaskHistory, AutoTaskHistoryView> taskHistoryEntityToView = AutoTaskHistoryView::new;
    private static final BaseConvert<AutoTaskHistoryArgParam, AutoTaskHistoryArg> taskHistoryArgParamToEntity = AutoTaskHistoryArg::new;
    private static final BaseConvert<AutoTaskHistoryArg, AutoTaskHistoryArgView> taskHistoryArgEntityToView = AutoTaskHistoryArgView::new;
    private static final BaseConvert<AutoTaskTemplate, AutoTaskTemplateView> taskTemplateEntityToView = AutoTaskTemplateView::new;
    private static final BaseConvert<AutoTaskTemplateArg, AutoTaskTemplateArgView> taskTemplateArgEntityToView = AutoTaskTemplateArgView::new;
    private static final BaseConvert<AutoSshServer, AutoSshServerView> sshServerEntityToView = AutoSshServerView::new;

    private final BusProperties busProperties;
    private final ApplicationEventPublisher applicationEventPublisher;

    private final Boolean rescheduleWhenStart;
    private final Scheduler scheduler;
    private final TransactionTemplate transactionTemplate;
    private final AutoSshServerDao sshServerDao;
    private final AutoTaskTemplateDao taskTemplateDao;
    private final AutoTaskTemplateArgDao taskTemplateArgDao;
    private final AutoTaskHistoryDao taskHistoryDao;
    private final AutoTaskHistoryArgDao taskHistoryArgDao;
    private final AutoTaskHistoryServerDao taskHistoryServerDao;
    private final UserFeign userFeign;

    @Autowired
    public AutoTaskHistoryService(@Value("${spring.quartz.rescheduleWhenStart:false}") Boolean rescheduleWhenStart,
                                  BusProperties busProperties,
                                  ApplicationEventPublisher applicationEventPublisher,
                                  Scheduler scheduler,
                                  TransactionTemplate transactionTemplate,
                                  AutoSshServerDao sshServerDao,
                                  AutoTaskTemplateDao taskTemplateDao,
                                  AutoTaskTemplateArgDao taskTemplateArgDao,
                                  AutoTaskHistoryDao taskHistoryDao,
                                  AutoTaskHistoryArgDao taskHistoryArgDao,
                                  AutoTaskHistoryServerDao taskHistoryServerDao,
                                  UserFeign userFeign) {
        super(taskHistoryDao, taskHistoryParamToEntity, taskHistoryEntityToView);
        this.busProperties = busProperties;
        this.applicationEventPublisher = applicationEventPublisher;
        this.rescheduleWhenStart = rescheduleWhenStart;
        this.scheduler = scheduler;
        this.transactionTemplate = transactionTemplate;
        this.sshServerDao = sshServerDao;
        this.taskTemplateDao = taskTemplateDao;
        this.taskTemplateArgDao = taskTemplateArgDao;
        this.taskHistoryDao = taskHistoryDao;
        this.taskHistoryArgDao = taskHistoryArgDao;
        this.taskHistoryServerDao = taskHistoryServerDao;
        this.userFeign = userFeign;
    }

    @Override
    protected AutoTaskHistoryView fill(AutoTaskHistoryView view) {
        Optional<AutoTaskHistory> opt = taskHistoryDao.findById(view.getId());
        opt.map(AutoTaskHistory::getAutoTaskTemplateId).flatMap(taskTemplateDao::findById).map(taskTemplateEntityToView).ifPresent(view::setTemplate);
        opt.map(AutoTaskHistory::getOwnerId).map(userFeign::get).ifPresent(view::setOwner);

        view.setArgs(new ArrayList<>());
        for (AutoTaskHistoryArg arg : taskHistoryArgDao.findByAutoTaskHistoryId(view.getId())) {
            AutoTaskHistoryArgView argView = taskHistoryArgEntityToView.apply(arg);
            taskTemplateArgDao.findById(arg.getAutoTaskTemplateArgId()).map(taskTemplateArgEntityToView).ifPresent(argView::setArg);
            view.getArgs().add(argView);
        }

        view.setServers(new ArrayList<>());
        for (AutoTaskHistoryServer autoTaskHistoryServer : taskHistoryServerDao.findByAutoTaskHistoryId(view.getId())) {
            sshServerDao.findById(autoTaskHistoryServer.getAutoSshServerId()).map(sshServerEntityToView).ifPresent(view.getServers()::add);
        }
        return view;
    }

    public PageResult<AutoTaskHistoryView> pageByKeyword(PageParam pageParam, String keyword) {
        Page<AutoTaskHistory> page;
        if (StringUtils.isEmpty(keyword)) {
            page = taskHistoryDao.findAll(new SpringPageParamAdapter(pageParam));
        } else {
            page = taskHistoryDao.findByNameLike('%' + keyword + '%', new SpringPageParamAdapter(pageParam));
        }
        PageResult<AutoTaskHistory> pageResult = new SpringPageResultAdapter<>(page);
        return pageResult.convert(taskHistoryEntityToView).convert(this::fill);
    }

    private void validate(AutoTaskHistoryParam param) {
        Assert.notNull(param.getAutoTaskTemplateId(), "Param autoTaskTemplateId can't be empty");
        Assert.notNull(param.getName(), "Param name can't be empty");
        Assert.notEmpty(param.getServers(), "Param servers can't be empty");
        if (param.getArgs() != null) {
            for (AutoTaskHistoryArgParam argParam : param.getArgs()) {
                Assert.notNull(argParam.getAutoTaskTemplateArgId(), "Param args[x].autoTaskTemplateArgId can't be empty");
                Assert.notNull(argParam.getValue(), "Param args[x].value can't be empty");
            }
        }
    }

    private JobKey getJobKey(String id) {
        return new JobKey(id, AutoTaskHistoryExecuteJob.class.getName());
    }

    private JobDetail getJobDetail(String id) {
        Map<String, Object> jobParam = new HashMap<String, Object>() {{
            this.put("id", id);
        }};
        return JobBuilder.newJob(AutoTaskHistoryExecuteJob.class)
                .withIdentity(this.getJobKey(id))
                .usingJobData(new JobDataMap(jobParam))
                .build();
    }

    private Trigger getJobTrigger(String id) {
        return TriggerBuilder.newTrigger()
                .withIdentity(id, AutoTaskHistoryExecuteJob.class.getName())
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(1, 1))
                .build();
    }

    @Override
    public AutoTaskHistoryView create(AutoTaskHistoryParam param) {
        if (StringUtils.isEmpty(param.getId())) {
            param.setId(UUID.randomUUID().toString());
        }

        this.validate(param);

        // check owner
        UserView owner = userFeign.get(param.getOwnerId());
        if (owner == null) {
            throw new HttpException(HttpStatus.BAD_REQUEST, Constant.HISTORY_SAVE_OWNER_ERROR);
        }

        // check name exists
        Optional<AutoTaskHistory> historyByNameOpt = taskHistoryDao.findByName(param.getName());
        if (historyByNameOpt.isPresent() && !historyByNameOpt.get().getId().equals(param.getId())) {
            throw new HttpException(HttpStatus.CONFLICT, Constant.HISTORY_SAVE_NAME_USED);
        }

        // save
        AutoTaskHistoryView result = transactionTemplate.execute(transactionStatus -> {
            AutoTaskHistory entity = taskHistoryParamToEntity.apply(param);
            entity.setStatus(AutoTaskHistoryStatus.LOADING);
            AutoTaskHistoryView view = taskHistoryEntityToView.apply(taskHistoryDao.save(entity));

            taskHistoryServerDao.deleteByAutoTaskHistoryId(view.getId());
            for (int i = 0; i < param.getServers().size(); i++) {
                AutoTaskHistoryServer server = new AutoTaskHistoryServer();
                server.setAutoTaskHistoryId(view.getId());
                server.setId(UUID.randomUUID().toString());
                server.setAutoSshServerId(param.getServers().get(i));
                server.setServerIndex(i + 1);
                taskHistoryServerDao.save(server);
            }

            taskHistoryArgDao.deleteByAutoTaskHistoryId(view.getId());
            for (AutoTaskHistoryArg arg : param.getArgs().stream().map(taskHistoryArgParamToEntity).collect(Collectors.toList())) {
                arg.setAutoTaskHistoryId(view.getId());
                arg.setId(UUID.randomUUID().toString());
                taskHistoryArgDao.save(arg);
            }

            return view;
        });

        // execute
        try {
            scheduler.scheduleJob(this.getJobDetail(result.getId()), this.getJobTrigger(result.getId()));
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        WebSocketEventPublisher eventPublisher = new WebSocketEventPublisher(busProperties, applicationEventPublisher);
        eventPublisher.send(new WebSocketEvent(WebSocketEvent.Type.AUTO_TASK_HISTORY_REFRESH, "CREATE"));

        return result;
    }

    @Override
    public AutoTaskHistoryView modify(AutoTaskHistoryParam param) {
        throw new HttpException(HttpStatus.METHOD_NOT_ALLOWED, Constant.HISTORY_MODIFY_REJECT);
    }

    @Override
    public void remove(String id) {
        Optional<AutoTaskHistory> history = taskHistoryDao.findById(id);
        if (!history.isPresent()) {
            throw new HttpException(HttpStatus.NOT_FOUND);
        }

        transactionTemplate.executeWithoutResult(transactionStatus -> {
            taskHistoryDao.deleteById(history.get().getId());
            taskHistoryServerDao.deleteByAutoTaskHistoryId(history.get().getId());
            taskHistoryArgDao.deleteByAutoTaskHistoryId(history.get().getId());
        });
    }
}
